package org.swing;

import com.jgoodies.validation.*;
import com.jgoodies.validation.view.ValidationComponentUtils;
import org.autocomplete.AutoCompleteDecorator;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.Vector;

/**
 * Created by babitsky.viacheslav
 * Date: 08.02.2008
 * Time: 21:10:15
 */
public class JExtComboBox extends JComboBox implements Validatable {
    private Vector data;
    private IconFeedbackPanel iconFeedbackPanel;
    private Validator validator;
    private ValidationType validationType;
    private PropertyChangeListener propertyChangeListener;
    private int serialValue;
    private Vector<ValidationMessage> validationMessageVector;
    private boolean canBeNullOnNonEditable = false;

    /**
     * Creates a <code>JComboBox</code> with a default data model.
     * The default data model is an empty list of objects.
     * Use <code>addItem</code> to add items.  By default the first item
     * in the data model becomes selected.
     *
     * @see javax.swing.DefaultComboBoxModel
     */
    public JExtComboBox() {
        super();
        serialValue = ValidationComponentUtils.getNextSerialValue();
        validationMessageVector = new Vector<ValidationMessage>();
        ValidationComponentUtils.setMessageKey(this, serialValue);
        ValidationComponentUtils.setMessageKey((JComponent) getEditor().getEditorComponent(), serialValue);
    }

    public Vector getData() {
        return data;
    }

    public void setData(Vector data) {
        this.data = data;
        setModel(new DefaultComboBoxModel(data));
    }

    public static Vector<ItemDecorator> decorate(Vector<Object> data, Vector<String> names) {
        Vector<ItemDecorator> decoratorVector = new Vector<ItemDecorator>(data.size());
        for (int i = 0; i < data.size(); i++) {
            decoratorVector.add(new ItemDecorator(data.elementAt(i), names.elementAt(i)));
        }
        return decoratorVector;
    }

    public static Vector<ItemDecorator> decorate(Object[] data, String[] names) {
        Vector<ItemDecorator> decoratorVector = new Vector<ItemDecorator>(data.length);

        for (int i = 0; i < data.length; i++) {
            decoratorVector.add(new ItemDecorator(data[i], names[i]));
        }
        return decoratorVector;
    }

    public static ItemDecorator decorate(Object data, String names) {
        return new ItemDecorator(data, names);
    }

    public static ItemDecorator decorate(Object item) {
        return new ItemDecorator(item);
    }

    public void setSelectedNonDecoratedItem(Object item) {
        setSelectedItem(decorate(item));
    }

    /**
     * This protected method is implementation specific. Do not access directly
     * or override.
     */
    @Override
    protected void selectedItemChanged() {
        Object old_value = selectedItemReminder;
        Object new_value = dataModel.getSelectedItem();
        super.selectedItemChanged();
        if (old_value != new_value) firePropertyChange("value", old_value, new_value);
    }

    /**
     * Sets the selected item in the combo box display area to the object in
     * the argument.
     * If <code>anObject</code> is in the list, the display area shows
     * <code>anObject</code> selected.
     * <p/>
     * If <code>anObject</code> is <i>not</i> in the list and the combo box is
     * uneditable, it will not change the current selection. For editable
     * combo boxes, the selection will change to <code>anObject</code>.
     * <p/>
     * If this constitutes a change in the selected item,
     * <code>ItemListener</code>s added to the combo box will be notified with
     * one or two <code>ItemEvent</code>s.
     * If there is a current selected item, an <code>ItemEvent</code> will be
     * fired and the state change will be <code>ItemEvent.DESELECTED</code>.
     * If <code>anObject</code> is in the list and is not currently selected
     * then an <code>ItemEvent</code> will be fired and the state change will
     * be <code>ItemEvent.SELECTED</code>.
     * <p/>
     * <code>ActionListener</code>s added to the combo box will be notified
     * with an <code>ActionEvent</code> when this method is called.
     *
     * @param value the list object to select; use <code>null</code> to
     *              clear the selection
     * @beaninfo preferred:   true
     * description: Sets the selected item in the JComboBox.
     */
    @Override
    public void setSelectedItem(Object value) {
        if (value instanceof String) {
            for (int i = 0; i < dataModel.getSize(); i++) {
                Object element = dataModel.getElementAt(i);
                if (element.equals(value)) {
                    setSelectedIndex(i);
                    return;
                }
            }
        }
        super.setSelectedItem(value);
    }

    /**
     * This method is public as an implementation side effect.
     * do not call or override.
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        Object value = getEditor().getItem();
        if (value instanceof String) {
            if (value.equals("")) {
                super.setSelectedItem(null);
                return;
            }
            for (int i = 0; i < dataModel.getSize(); i++) {
                Object element = dataModel.getElementAt(i);
                if (element.equals(value)) {
                    getEditor().setItem(element);
                    break;
                }
            }
        }
        super.actionPerformed(e);
    }

    public void setValue(Object value) {
        if (value == null) {
            setSelectedItem(null);
            return;
        }
        for (int i = 0; i < dataModel.getSize(); i++) {
            Object element = dataModel.getElementAt(i);
            if (element.equals(value)) {
                setSelectedIndex(i);
                return;
            }
        }
        setSelectedItem(null);
    }


    public Object getValue() {
        Object obj = getSelectedItem();
        if (obj instanceof ItemDecorator) return ((ItemDecorator) obj).getValue();
        return obj;
    }

    /**
     * Processes focus events occurring on this component by
     * dispatching them to any registered
     * <code>FocusListener</code> objects.
     * <p/>
     * This method is not called unless focus events are
     * enabled for this component. Focus events are enabled
     * when one of the following occurs:
     * <p><ul>
     * <li>A <code>FocusListener</code> object is registered
     * via <code>addFocusListener</code>.
     * <li>Focus events are enabled via <code>enableEvents</code>.
     * </ul>
     * <p/>
     * If focus events are enabled for a <code>Component</code>,
     * the current <code>KeyboardFocusManager</code> determines
     * whether or not a focus event should be dispatched to
     * registered <code>FocusListener</code> objects.  If the
     * events are to be dispatched, the <code>KeyboardFocusManager</code>
     * calls the <code>Component</code>'s <code>dispatchEvent</code>
     * method, which results in a call to the <code>Component</code>'s
     * <code>processFocusEvent</code> method.
     * <p/>
     * If focus events are enabled for a <code>Component</code>, calling
     * the <code>Component</code>'s <code>dispatchEvent</code> method
     * with a <code>FocusEvent</code> as the argument will result in a
     * call to the <code>Component</code>'s <code>processFocusEvent</code>
     * method regardless of the current <code>KeyboardFocusManager</code>.
     * <p/>
     * <p>Note that if the event parameter is <code>null</code>
     * the behavior is unspecified and may result in an
     * exception.
     *
     * @param e the focus event
     * @see java.awt.event.FocusEvent
     * @see java.awt.event.FocusListener
     * @see java.awt.KeyboardFocusManager
     * @see #addFocusListener
     * @see #enableEvents
     * @see #dispatchEvent
     * @since JDK1.1
     */
    @Override
    protected void processFocusEvent(FocusEvent e) {
        super.processFocusEvent(e);
        if (e.getID() == FocusEvent.FOCUS_LOST) {
            if (validationType == validateOnLostFocus) {
                if (JExtComboBox.this.getParent() != null) {
                    JExtComboBox.this.iconFeedbackPanel.setOwnMessagesAndRevalidateOtherRegistredComponent(getSerialValue(), validateValue(), true);
                }
            }
        }
    }

    public void addValidater(IconFeedbackPanel iconFeedbackPanel, Validator validator, ValidationType validationType) {
        this.iconFeedbackPanel = iconFeedbackPanel;
        this.validator = validator;
        this.validationType = validationType;
        iconFeedbackPanel.registerComponent(this);
        if (validationType == validateOnValueChanged) {
            propertyChangeListener = new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    if (JExtComboBox.this.getParent() != null) {
                        JExtComboBox.this.iconFeedbackPanel.setOwnMessagesAndRevalidateOtherRegistredComponent(getSerialValue(), validateValue(), true);
                    }
                }
            };
            addPropertyChangeListener("value", propertyChangeListener);
        }

    }

    public void removeValidater() {
        validator = null;
        validationType = null;
        if (propertyChangeListener != null) {
            removePropertyChangeListener(propertyChangeListener);
            propertyChangeListener = null;
        }

    }

    /**
     * Validates this object and returns a {@link com.jgoodies.validation.ValidationResult}.
     *
     * @return the validation result
     */
    public Vector<ValidationMessage> validateValue() {
        if (validator != null) {
            return validator.validate(getSelectedItem(), serialValue);
        }
        return validationMessageVector;
    }

    public int getSerialValue() {
        return serialValue;
    }

    public void setMandarory(boolean mandatory) {
        ValidationComponentUtils.setMandatory(this, mandatory);
    }

    public boolean isMandatory() {
        return ValidationComponentUtils.isMandatory(this);
    }

    public void setUpAutoComplete() {
        AutoCompleteDecorator.decorate(this);
    }

   /**
     * Sets the object that translates a keyboard character into a list
     * selection. Typically, the first selection with a matching first
     * character becomes the selected item.
     *
     * @beaninfo expert: true
     * description: The objects that changes the selection when a key is pressed.
     */
    @Override
    public void setKeySelectionManager(final KeySelectionManager manager) {
        if (manager == null) {
            super.setKeySelectionManager(null);
            return;
        }
        KeySelectionManager selectionManager = new KeySelectionManager() {
            public int selectionForKey(char aKey, ComboBoxModel aModel) {
                if (!isEditable() && isCanBeNullOnNonEditable() && (aKey == KeyEvent.VK_DELETE || aKey == KeyEvent.VK_BACK_SPACE)){
                    setSelectedItem(null);
                    return -1;
                }
                return manager.selectionForKey(aKey, aModel);
            }
        };
        super.setKeySelectionManager(selectionManager);
    }

    public boolean isCanBeNullOnNonEditable() {
        return canBeNullOnNonEditable;
    }

    public void setCanBeNullOnNonEditable(boolean canBeNullOnNonEditable) {
        this.canBeNullOnNonEditable = canBeNullOnNonEditable;
    }
}

class ItemDecorator implements Serializable {
    private Object value;
    private String name;
    private static final long serialVersionUID = 8330929312011830168L;

    ItemDecorator(Object value) {
        this(value, value.toString());
    }

    ItemDecorator(Object value, String name) {
        assert value == null;
        assert name == null;
        this.value = value;
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (obj instanceof ItemDecorator) return value.equals(((ItemDecorator) obj).value);
        if (obj instanceof String && !(value instanceof String)) return name.equals(obj);
        return value.equals(obj);
    }

    public String getName() {
        return name;
    }

    public Object getValue() {
        return value;
    }

    public String toString() {
        return name == null ? "" : name;
    }

    public String toStringDebug() {
        return "ItemDecorator{" +
               "value=" + value +
               ", name='" + name + '\'' +
               '}';
    }
}


